package com.orange.cloud.common.redis.config;


import com.orange.cloud.common.redis.utils.CodecUtils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.List;
import java.util.UUID;

@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

    private static Logger logger = LoggerFactory.getLogger(CacheConfig.class);

    @Value("${spring.application.name}")
    private String applicationName;

    /**
     * 配置缓存管理器
     *
     * @param redisConnectionFactory 连接工厂
     * @param cacheKeyPrefix         缓存key的前缀
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, CacheKeyPrefix cacheKeyPrefix) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        //取出数据序列化配置
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(mapper);
        //存入数据序列化配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                //配置缓存失效时间10min
                .entryTtl(Duration.ofSeconds(600))
                //缓存key的前缀
                .computePrefixWith(cacheKeyPrefix)
                //key序列化方式
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                //value序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));

        RedisCacheManager cacheManager = RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

    /**
     * 缓存key前缀，spring项目名:{cacheNames}
     *
     * @return
     */
    @Bean
    public CacheKeyPrefix cacheKeyPrefix() {
        return cacheName -> {
            StringBuilder sb = new StringBuilder(100);
            //spring项目名
            sb.append(applicationName).append(":");
            //缓存名（用户自定义）
            sb.append(cacheName).append(":");
            return sb.toString();
        };
    }

    /**
     * 自定义redis key生成器
     * 默认key的生成规则：key的前缀(项目名:缓存名):简单类名:方法名:参数名（多个参数#号，List和非简单类型都有特殊处理）
     */
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return (o, method, params) -> {
            StringBuilder sb = new StringBuilder();
            // 类名
            sb.append(o.getClass().getSimpleName())
                    .append(":")
                    // 方法名
                    .append(method.getName())
                    .append(":");
            // 参数名
            int argIndex = 0;
            for (Object param : params) {
                if (argIndex > 0) {
                    //多个参数用'#'号隔开
                    sb.append("#");
                }
                sb.append(getKeyString(param));
                argIndex++;
            }
            return sb.toString();
        };
    }

    /**
     * redis数据操作异常处理
     * 这里的处理：在日志中打印出错误信息，但是放行
     * 保证redis服务器出现连接等问题的时候不影响程序的正常运行，使得能够出问题时不用缓存
     *
     * @return
     */
    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                redisErrorException(exception, key);
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                redisErrorException(exception, key);
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                redisErrorException(exception, key);
            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                redisErrorException(exception, null);
            }
        };
        return cacheErrorHandler;
    }

    protected void redisErrorException(Exception exception, Object key) {
        logger.error("redis异常：key=[{}]", key, exception);
    }

    /**
     * 方法参数处理成rediskey的方式
     *
     * @param obj
     * @return
     */
    private String getKeyString(Object obj) {
        if (obj == null) {
            return "NULL";
        }
        try {

            if (BeanUtils.isSimpleValueType(obj.getClass())) {
                //简单类型参数处理方式
                return obj.toString();
            } else if (obj instanceof List) {
                //List对象参数处理方式
                return "List-" + ((List<?>) obj).size() + "@" + CodecUtils.MD5(new ObjectMapper().writeValueAsString(obj));
            } else {
                //其他对象参数处理方式
                return obj.getClass().getSimpleName() + "@" + CodecUtils.MD5(new ObjectMapper().writeValueAsString(obj));
            }
        } catch (Exception e) {
            e.printStackTrace();
            return obj.getClass().getSimpleName() + "@" + UUID.randomUUID();
        }
    }


}
